package com.plpm.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.plpm.dto.FileDownloadResult;
import com.plpm.dto.FileValidationResult;
import com.plpm.exception.ApiException;
import com.plpm.exception.FileDownloadException;
import com.plpm.mapper.FileRecordMapper;
import com.plpm.model.BaseDataFile;
import com.plpm.model.ImportTask;
import com.plpm.model.Result;
import com.plpm.vo.FileRecordVO;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpException;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * PLPM接口调用服务
 *
 * @author PLPM Team
 * @version 1.0
 */
@Slf4j
@Service
public class PLPMService {

    @Autowired
    private IoTDBService iotdbService;

    @Autowired
    private FileRecordMapper fileRecordMapper;

    @Autowired
    private FileRecordService fileRecordService;

    @Autowired
    private ImportTaskService importTaskService;

    @Value("${plpm.base-url}")
    private String plpmBaseUrl;

    @Value("${plpm.queryByUploadTime-url}")
    private String queryByUploadTimeUrl;

    @Value("${plpm.downloadByFileIndex-url}")
    private String downloadByFileIndexUrl;

    @Value("${plpm.api.import-records}")
    private String importRecordsApi;

    @Value("${plpm.api.download-file}")
    private String downloadFileApi;

    @Value("${file.upload.path}")
    private String uploadPath;

    @Value("${plpm.retry.max-attempts:3}")
    private int maxRetryAttempts;

    @Value("${plpm.retry.delay-seconds:30}")
    private int retryDelaySeconds;

    @Value("${plpm.retry.hash-validation.max-attempts:3}")
    private int maxHashValidationAttempts;

    @Value("${plpm.retry.hash-validation.delay-seconds:60}")
    private int hashValidationDelaySeconds;

    @Value("${plpm.file.cleanup.enabled:true}")
    private boolean fileCleanupEnabled;

    @Value("${plpm.file.cleanup.retention-days:7}")
    private int fileCleanupRetentionDays;

    @Value("${plpm.file.cleanup.cron:0 0 2 * * ?}")
    private String fileCleanupCron;


    /**
     * 定时任务：根据时间查询去plpm文件记录
     */
    // todo 记得放开
    // @Scheduled(cron = "${plpm.schedule.cron}")
    public void checkNewDownLoadTask() {
        executeDownloadTask();
    }

    /**
     * 获取最新的uploadTime值
     *
     * @return 格式化的时间字符串，格式为 "yyyy-MM-dd HH:mm:ss"，如果没有找到记录则返回null
     */
    private String getLatestUploadTime() {
        try {
            String latestUploadTime = fileRecordMapper.findLatestUploadTime();
            if (StringUtils.isNotEmpty(latestUploadTime)) {
                // SQLite的datetime格式通常是 "yyyy-MM-dd HH:mm:ss"，直接返回
                //若是"2025-10-14T18:01:19.28"，需要转换为"2025-10-14 18:01:19"
                //latestUploadTime = "2025-07-10 18:01:19";
                log.info("从数据库获取到最新uploadTime: {}", latestUploadTime);

                // 检查时间格式并进行转换
                if (latestUploadTime.contains("T")) {
                    // 格式为 "2025-10-14T18:01:19.284967700"，需要转换为 "2025-10-14 18:01:19"
                    try {
                        // 移除T，截取到秒的部分
                        String convertedTime = latestUploadTime.replace("T", " ");
                        if (convertedTime.contains(".")) {
                            convertedTime = convertedTime.substring(0, convertedTime.indexOf("."));
                        }
                        log.info("时间格式转换: {} -> {}", latestUploadTime, convertedTime);
                        return convertedTime;
                    } catch (Exception e) {
                        log.warn("时间格式转换失败，返回原始格式: {}", latestUploadTime, e);
                        return latestUploadTime;
                    }
                } else {
                    // 已经是正确格式，直接返回
                    return latestUploadTime;
                }
            } else {
                // 如果没有找到记录，返回null，让调用方决定如何处理
                log.warn("未找到uploadTime记录");
                return null;
            }
        } catch (Exception e) {
            log.error("获取最新uploadTime失败", e);
            // 发生异常时返回null，让调用方决定如何处理
            return null;
        }
    }

    /**
     * 执行PLPM文件下载任务的核心业务逻辑
     *
     * @return 任务执行结果，包含处理文件数量等信息
     */
    public Map<String, Object> executeDownloadTask() {
        Map<String, Object> result = new HashMap<>();
        String taskId = UUID.randomUUID().toString().substring(0, 8);
        MDC.put("taskId", taskId);
        try {
            log.info("开始执行PLPM文件下载任务，taskId: {}", taskId);

            // 1. 获取查询基准时间
            String uploadTime = getLatestUploadTime();
            //todo 记得放开
            uploadTime = "2025-11-03 00:00:00";
            if (StringUtils.isBlank(uploadTime)) {
                log.info("未找到有效的uploadTime，任务结束");
                result.put("success", false);
                result.put("message", "未找到有效的uploadTime");
                result.put("processedCount", 0);
                return result;
            }

            // 2. 从外部API获取文件列表
            List<BaseDataFile> files;
            try {
                files = fetchFilesFromExternalAPI(uploadTime);
                if (files.isEmpty()) {
                    log.info("未获取到新文件，任务结束");
                    result.put("success", true);
                    result.put("message", "未获取到新文件");
                    result.put("processedCount", 0);
                    return result;
                }
            } catch (ApiException e) {
                log.error("获取文件列表失败", e);
                result.put("success", false);
                result.put("message", "获取文件列表失败: " + e.getMessage());
                result.put("processedCount", 0);
                return result;
            }

            // 3. 处理文件列表
            processFileList(files);

            log.info("PLPM文件下载任务执行完成，处理文件数量: {}", files.size());
            result.put("success", true);
            result.put("message", "任务执行成功");
            result.put("processedCount", files.size());
            result.put("uploadTime", uploadTime);

        } catch (Exception e) {
            log.error("PLPM文件下载任务执行失败", e);
            handleTaskFailure(e);
            result.put("success", false);
            result.put("message", "任务执行失败: " + e.getMessage());
            result.put("processedCount", 0);
        } finally {
            MDC.remove("taskId");
        }
        return result;
    }

    /**
     * 从外部API获取文件列表
     *
     * @param uploadTime 查询基准时间
     * @return 文件列表
     * @throws ApiException API调用异常
     */
    @org.springframework.retry.annotation.Retryable(
        value = {ApiException.class, HttpException.class},
        maxAttempts = 3,
        backoff = @org.springframework.retry.annotation.Backoff(delay = 1000, multiplier = 2)
    )
    private List<BaseDataFile> fetchFilesFromExternalAPI(String uploadTime) throws ApiException {
        log.info("开始调用外部API获取文件列表，uploadTime: {}", uploadTime);

        try {
            // 构建API请求
            HttpResponse response = buildApiRequest(uploadTime);

            // 验证响应状态
            validateApiResponse(response);

            // 解析响应数据
            return parseApiResponse(response);

        } catch (HttpException e) {
            log.error("HTTP请求失败: {}", e.getMessage());
            throw new ApiException("HTTP请求失败", e);
        }
    }

    /**
     * 构建API请求
     */
    private HttpResponse buildApiRequest(String uploadTime) {
        log.info("Calling external API: {} with uploadTime: {}", queryByUploadTimeUrl, uploadTime);
        return HttpRequest.get(queryByUploadTimeUrl)
                .form("uploadTime", uploadTime)
                .timeout(30000)
                .execute();
    }

    /**
     * 验证API响应
     */
    private void validateApiResponse(HttpResponse response) throws ApiException {
        if (response.getStatus() != 200) {
            String errorMsg = String.format("API调用失败，状态码: %d, 响应: %s",
                    response.getStatus(), response.body());
            log.error(errorMsg);
            throw new ApiException(errorMsg);
        }
    }

    /**
     * 解析API响应
     */
    private List<BaseDataFile> parseApiResponse(HttpResponse response) throws ApiException {
        String responseBody = response.body();
        log.info("External API response: {}", responseBody);

        // 将JSON字符串转换为Result<List<BaseDataFile>>对象
        @SuppressWarnings("unchecked")
        Result<List<BaseDataFile>> result = JSONUtil.toBean(responseBody, Result.class);

        if (!result.isSuccess()) {
            String errorMsg = String.format("API返回失败: code=%d, msg=%s",
                    result.getCode(), result.getMessage());
            log.error(errorMsg);
            throw new ApiException(errorMsg);
        }

        // 转换为BaseDataFile对象列表
        return JSONUtil.toList(JSONUtil.toJsonStr(result.getResult()), BaseDataFile.class);
    }

    /**
     * 处理文件列表
     *
     * @param files 文件列表
     */
    private void processFileList(List<BaseDataFile> files) {
        log.info("开始处理文件列表，文件数量: {}", files.size());

        int successCount = 0;
        int failCount = 0;

        for (BaseDataFile baseDataFile : files) {
            String importFileIndex = baseDataFile.getImportFileIndex() != null 
                    ? String.valueOf(baseDataFile.getImportFileIndex()) : "unknown";
            MDC.put("importFileIndex", importFileIndex);
            try {
                processSingleFile(baseDataFile);
                successCount++;
            } catch (ApiException e) {
                // ApiException产生记录，保存文件记录到file_record表，syncStatus=0，未拉取（API请求失败）
                log.error("文件下载请求失败，importFileIndex: {}", baseDataFile.getImportFileIndex(), e);
                failCount++;
                recordFileProcessingFailure(baseDataFile, e, 0); // syncStatus=0，未拉取
            } catch (FileDownloadException e) {
                // FileDownloadException产生记录，保存文件记录到file_record表，根据异常中的syncStatus设置
                log.error("文件下载失败，importFileIndex: {}, syncStatus: {}", 
                        baseDataFile.getImportFileIndex(), e.getSyncStatus(), e);
                failCount++;
                recordFileProcessingFailure(baseDataFile, e, e.getSyncStatus());
            } catch (Exception e) {
                log.error("处理文件时发生未知异常，importFileIndex: {}", baseDataFile.getImportFileIndex(), e);
                failCount++;
                recordFileProcessingFailure(baseDataFile, e, 2); // syncStatus=2，拉取失败
            } finally {
                MDC.remove("importFileIndex");
            }
        }

        log.info("文件处理完成，成功: {}, 失败: {}", successCount, failCount);
    }

    /**
     * 处理单个文件
     *
     * @param baseDataFile 基础数据文件对象
     */
    private void processSingleFile(BaseDataFile baseDataFile) throws FileDownloadException, ApiException {
        log.info("开始处理文件，importFileIndex: {}", baseDataFile.getImportFileIndex());

        // 1. 下载文件(为避免多次磁盘I/O,保存文件流的同时计算hash值()
        FileDownloadResult downloadResult = downloadFile(baseDataFile);

        // 2. 验证文件
        FileValidationResult validationResult = validateDownloadedFile(downloadResult);

        // 3. 保存文件记录到file_record表
        FileRecordVO fileRecord = createFileRecord(baseDataFile, downloadResult, validationResult);
        saveFileRecord(fileRecord);

        // 4. 导入到IoTDB（如果验证通过）
        if (validationResult.isValid()) {
            importFileToIoTDB(fileRecord, downloadResult.getLocalFilePath());
        }

        log.info("文件处理完成，importFileIndex: {}", baseDataFile.getImportFileIndex());
    }

    /**
     * 下载文件
     */
    @org.springframework.retry.annotation.Retryable(
        value = {IOException.class, HttpException.class},
        maxAttempts = 3,
        backoff = @org.springframework.retry.annotation.Backoff(delay = 2000, multiplier = 2),
        exclude = {ApiException.class}  // API请求失败不重试
    )
    private FileDownloadResult downloadFile(BaseDataFile baseDataFile) throws FileDownloadException, ApiException {
        log.info("开始下载文件，importFileIndex: {}", baseDataFile.getImportFileIndex());

        try {
            // 1.构建下载请求
            HttpResponse response = buildDownloadRequest(baseDataFile);

            // 2.验证下载响应
            if (response.getStatus() != 200) {
                String errorMsg = String.format("文件请求下载失败，importFileIndex: %s, 状态码: %d, 响应: %s",
                        baseDataFile.getImportFileIndex(), response.getStatus(), response.body());
                log.error(errorMsg);
                throw new ApiException(errorMsg); // API请求失败，未拉取
            }
            // 3.保存文件到本地
            return saveFileToLocal(response, baseDataFile);

        } catch (Exception e) {
            log.error("文件下载失败，importFileIndex: {}", baseDataFile.getImportFileIndex(), e);
            throw new FileDownloadException("文件下载失败", 2, e); // syncStatus=2，拉取失败
        }
    }

    /**
     * 构建下载请求
     */
    private HttpResponse buildDownloadRequest(BaseDataFile baseDataFile) {
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("importFileIndex", baseDataFile.getImportFileIndex());

        return HttpRequest.post(downloadByFileIndexUrl)
                .header("Content-Type", "application/json")
                .header("Accept", "application/octet-stream")
                .body(JSONUtil.toJsonStr(requestBody))
                .timeout(30000)
                .execute();
    }

    /**
     * 保存文件到本地
     */
    private FileDownloadResult saveFileToLocal(HttpResponse response, BaseDataFile baseDataFile) throws IOException, NoSuchAlgorithmException {
        // 获取文件信息
        String contentDisposition = response.header("Content-Disposition");
        log.info("Content-Disposition头内容: {}", contentDisposition);
        String fileName = extractFileNameFromContentDisposition(contentDisposition);
        log.info("解析后的文件名: {}", fileName);
        String fileHash = response.header("X-File-Hash");

        // 保存文件并同时计算hash
        String localFilePath = uploadPath + fileName;
        FileDownloadResult result = saveFileStreamWithHash(response.bodyStream(), localFilePath, fileName, fileHash);

        log.info("文件下载成功, importFileIndex: {}, 文件大小: {} bytes, 文件保存路径: {}, hash验证: {}",
                baseDataFile.getImportFileIndex(), result.getFileSize(), localFilePath, 
                result.isHashValid() ? "通过" : "失败");

        return result;
    }

    /**
     * 保存文件流并同时计算hash值(避免多次磁盘I/O)
     */
    private FileDownloadResult saveFileStreamWithHash(InputStream inputStream, String localFilePath, String fileName, String fileHash) throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        
        try (FileOutputStream fos = new FileOutputStream(localFilePath)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            long totalBytesRead = 0;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 同时写入文件和更新hash
                fos.write(buffer, 0, bytesRead);
                md.update(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;
            }
            
            // 计算实际hash值
            byte[] digest = md.digest();
            String actualHash = bytesToHex(digest);
            
            // 验证hash
            boolean hashValid = StringUtils.isNotEmpty(fileHash) && fileHash.equals(actualHash);
            // hash验证不一致，则视为下载失败。记录到file_record表，同步状态设为2，拉取失败
            log.info("文件保存完成，实际hash: {}, 文件hash: {}, 验证结果: {}", 
                    actualHash, fileHash, hashValid ? "通过" : "失败");
            
            return new FileDownloadResult(localFilePath, fileName, fileHash, actualHash, totalBytesRead, hashValid);
        }
    }

    /**
     * 将字节数组转换为十六进制字符串
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }


    /**
     * 验证下载的文件
     */
    private FileValidationResult validateDownloadedFile(FileDownloadResult downloadResult) {
        try {
            // 如果hash验证已经在下载过程中完成，直接返回结果
            if (downloadResult.getActualHash() != null) {
                log.info("文件哈希验证结果: {}", downloadResult.isHashValid() ? "通过" : "失败");
                return new FileValidationResult(downloadResult.isHashValid(), 
                        downloadResult.isHashValid() ? "验证通过" : "哈希值不匹配");
            }

            // 兼容旧版本：如果没有实际hash值，使用旧方法验证
            if (StringUtils.isEmpty(downloadResult.getFileHash())) {
                log.warn("文件哈希值为空，跳过验证");
                return new FileValidationResult(true, "无哈希值，跳过验证");
            }

            // 验证文件哈希值
            boolean hashValid = validateFileHash(downloadResult.getLocalFilePath(), downloadResult.getFileHash());
            log.info("文件哈希验证结果: {}", hashValid ? "通过" : "失败");

            return new FileValidationResult(hashValid, hashValid ? "验证通过" : "哈希值不匹配");

        } catch (Exception e) {
            log.error("文件验证异常: {}", downloadResult.getLocalFilePath(), e);
            return new FileValidationResult(false, "验证异常: " + e.getMessage());
        }
    }

    /**
     * 创建文件记录
     */
    private FileRecordVO createFileRecord(BaseDataFile baseDataFile, FileDownloadResult downloadResult, FileValidationResult validationResult) {
        FileRecordVO fileRecordVO = new FileRecordVO();
        LocalDateTime now = LocalDateTime.now();

        // 设置基本信息
        fileRecordVO.setFileName(baseDataFile.getFileName());
        fileRecordVO.setProjectNo(baseDataFile.getProjectNo());
        fileRecordVO.setPlaneId(baseDataFile.getPlaneId());
        fileRecordVO.setPlaneNo(baseDataFile.getPlaneNo());
        fileRecordVO.setPlace(baseDataFile.getPlace());
        fileRecordVO.setCompany(baseDataFile.getCompany());
        fileRecordVO.setProductPhase(baseDataFile.getProductPhase());
        fileRecordVO.setDataTypeFirst(baseDataFile.getDataTypeFirst());
        fileRecordVO.setDataTypeSecond(baseDataFile.getDataTypeSecond());
        fileRecordVO.setDataSource(baseDataFile.getDataSource());

        // 设置时间信息
        if (baseDataFile.getStartTime() != null) {
            fileRecordVO.setStartTime(baseDataFile.getStartTime().toInstant()
                    .atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
        }
        fileRecordVO.setEngineId(baseDataFile.getEngineId());
        fileRecordVO.setEngineNo(baseDataFile.getEngineNo());
        fileRecordVO.setChannelNo(baseDataFile.getChannelNo());
        fileRecordVO.setEnginePlace(baseDataFile.getEnginePlace());
        fileRecordVO.setDeviceNo(baseDataFile.getDeviceNo());
        fileRecordVO.setEcuId(baseDataFile.getEcuId());
        fileRecordVO.setEcuNo(baseDataFile.getEcuNo());
        fileRecordVO.setEcuSysId(baseDataFile.getEcuSysId());
        fileRecordVO.setEcuSysNo(baseDataFile.getEcuSysNo());
        fileRecordVO.setSubject(baseDataFile.getSubject());
        
        // 设置remark：如果hash验证失败，记录验证失败信息；否则使用原始remark
        if (!validationResult.isValid()) {
            fileRecordVO.setRemark((StringUtils.isNotEmpty(baseDataFile.getRemark()) 
                    ? baseDataFile.getRemark() + " | " : "") + validationResult.getReason());
        } else {
            fileRecordVO.setRemark(baseDataFile.getRemark());
        }

        // 设置系统字段
        // 下载阶段，不需要赋值uploadStatus
        // fileRecordVO.setUploadStatus(1);
        fileRecordVO.setCreateTime(now);
        fileRecordVO.setUpdateTime(now);
        // 下载阶段，不需要赋值UploadTime
        // fileRecordVO.setUploadTime(now);
        fileRecordVO.setRetryTime(1);
        fileRecordVO.setSyncDataSource(1); // 1-苏家屯
        // 使用实际计算的hash值，如果没有则使用文件hash值
        String checkCode = downloadResult.getActualHash() != null ? 
                downloadResult.getActualHash() : downloadResult.getFileHash();
        fileRecordVO.setCheckCode(checkCode);

        // 根据hash值验证结果设置同步状态
        fileRecordVO.setSyncStatus(validationResult.isValid() ? 1 : 2); // 1-拉取成功，2-拉取失败

        return fileRecordVO;
    }


    /**
     * 带重试机制的根据importFileIndex下载文件
     *
     * @param baseDataFile 文件索引
     * @param attempt      当前尝试次数
     */
    private void downloadFileByIndexWithRetry(BaseDataFile baseDataFile, int attempt) {
        try {
            log.info("开始下载文件，importFileIndex: {}, 尝试次数: {}/{}",
                    baseDataFile.getImportFileIndex(), attempt, maxRetryAttempts);

            // 构建请求体
            java.util.Map<String, Object> requestBody = new java.util.HashMap<>();
            requestBody.put("importFileIndex", baseDataFile.getImportFileIndex());

            // 使用Hutool调用外部下载接口
            HttpResponse response = HttpRequest.post(downloadByFileIndexUrl)
                    .header("Content-Type", "application/json")
                    .header("Accept", "application/octet-stream")
                    .body(JSONUtil.toJsonStr(requestBody))
                    .timeout(30000)
                    .execute();

            if (response.getStatus() == 200) {
                // 获取文件信息
                String contentDisposition = response.header("Content-Disposition");
                log.info("Content-Disposition头内容: {}", contentDisposition);
                String fileName = extractFileNameFromContentDisposition(contentDisposition);
                log.info("解析后的文件名: {}", fileName);
                // String fileSize = response.header("X-File-Size");
                String fileHash = response.header("X-File-Hash");

                // 流式保存到本地文件并同时计算hash
                String localFilePath = uploadPath + fileName;
                FileDownloadResult downloadResult = saveFileStreamWithHash(response.bodyStream(), localFilePath, fileName, fileHash);
                
                log.info("文件下载成功, importFileIndex: {}, 文件大小: {} bytes, 文件保存路径: {}, hash验证: {}",
                        baseDataFile.getImportFileIndex(), downloadResult.getFileSize(), localFilePath, 
                        downloadResult.isHashValid() ? "通过" : "失败");
                
                // 处理已下载的文件
                processDownloadedFileWithRetry(baseDataFile, downloadResult, attempt);

            } else {
                // 下载失败，检查是否需要重试
                if (attempt < maxRetryAttempts) {
                    log.warn("文件下载失败，准备重试，importFileIndex: {}, 状态码: {}, 尝试次数: {}/{}",
                            baseDataFile.getImportFileIndex(), response.getStatus(), attempt, maxRetryAttempts);

                    // 等待重试间隔
                    Thread.sleep(retryDelaySeconds * 1000L);

                    // 重试下载
                    downloadFileByIndexWithRetry(baseDataFile, attempt + 1);
                } else {
                    log.error("文件下载失败，已达到最大重试次数，放弃下载，importFileIndex: {}, 状态码: {}, 响应: {}",
                            baseDataFile.getImportFileIndex(), response.getStatus(), response.body());
                }
            }

        } catch (Exception e) {
            log.error("下载文件异常，importFileIndex: {}, 尝试次数: {}",
                    baseDataFile.getImportFileIndex(), attempt, e);

            // 如果是网络异常等可重试的异常，且未达到最大重试次数，则重试
            if (attempt < maxRetryAttempts && isRetryableException(e)) {
                try {
                    log.warn("检测到可重试异常，准备重试下载，importFileIndex: {}, 尝试次数: {}/{}",
                            baseDataFile.getImportFileIndex(), attempt, maxRetryAttempts);

                    // 等待重试间隔
                    Thread.sleep(retryDelaySeconds * 1000L);

                    // 重试下载
                    downloadFileByIndexWithRetry(baseDataFile, attempt + 1);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    log.error("重试等待被中断，importFileIndex: {}", baseDataFile.getImportFileIndex());
                }
            } else {
                log.error("下载文件最终失败，importFileIndex: {}", baseDataFile.getImportFileIndex());
            }
        }
    }


    /**
     * 带重试机制的处理下载文件方法
     *
     * @param baseDataFile   基础数据文件对象
     * @param downloadResult 下载结果（包含hash验证信息）
     * @param attempt        当前尝试次数
     */
    private void processDownloadedFileWithRetry(BaseDataFile baseDataFile, FileDownloadResult downloadResult, int attempt) {
        try {
            log.info("开始处理下载的文件，importFileIndex: {}, 尝试次数: {}/{}",
                    baseDataFile.getImportFileIndex(), attempt, maxHashValidationAttempts);

            // 1. 获取hash验证结果（已在下载过程中完成）
            boolean hashValid = downloadResult.isHashValid();
            log.info("文件哈希验证结果: {}", hashValid ? "通过" : "失败");

            // 2. 创建FileRecordVO对象并设置字段值
            FileRecordVO fileRecordVO = createFileRecord(baseDataFile, downloadResult, 
                    new FileValidationResult(hashValid, hashValid ? "验证通过" : "哈希值不匹配"));

            // 3. 保存到file_record表
            saveFileRecord(fileRecordVO);

            // 4.1 如果哈希验证通过，执行导入逻辑
            if (hashValid) {
                importFileToIoTDB(fileRecordVO, downloadResult.getLocalFilePath());
                log.info("文件处理成功，importFileIndex: {}", baseDataFile.getImportFileIndex());
            } else {
                // 4.2 如果哈希验证不通过，检查是否需要重试
                if (attempt < maxHashValidationAttempts) {
                    log.warn("文件哈希验证失败，准备重试下载，importFileIndex: {}, 尝试次数: {}/{}",
                            baseDataFile.getImportFileIndex(), attempt, maxHashValidationAttempts);

                    // 删除当前文件
                    deleteFileIfExists(downloadResult.getLocalFilePath());

                    // 等待重试间隔
                    Thread.sleep(hashValidationDelaySeconds * 1000L);

                    // 重新下载文件
                    downloadFileByIndexWithRetry(baseDataFile, attempt + 1);
                } else {
                    log.error("文件哈希验证失败，已达到最大重试次数，放弃处理，importFileIndex: {}",
                            baseDataFile.getImportFileIndex());
                    // 删除失败的文件
                    deleteFileIfExists(downloadResult.getLocalFilePath());
                }
            }

        } catch (Exception e) {
            log.error("处理下载文件失败，importFileIndex: {}, 尝试次数: {}",
                    baseDataFile.getImportFileIndex(), attempt, e);

            // 如果是最后一次尝试，删除文件
            if (attempt >= maxHashValidationAttempts) {
                deleteFileIfExists(downloadResult.getLocalFilePath());
            }
        }
    }

    /**
     * 从Content-Disposition头中提取文件名
     * 支持多种格式：
     * 1. filename*=UTF-8''encoded_filename (RFC 5987标准)
     * 2. filename*=UTF-8"encoded_filename" (带引号的RFC 5987)
     * 3. filename="filename.ext" (标准格式)
     * 4. filename=filename.ext (无引号格式)
     *
     * @param contentDisposition Content-Disposition头内容
     * @return 文件名
     */
    private String extractFileNameFromContentDisposition(String contentDisposition) {
        if (StringUtils.isEmpty(contentDisposition)) {
            return "unknown_file.dat";
        }


        // 兼容性处理：如果Content-Disposition直接是URL编码的文件名（接口提供方错误实现）
        if (!contentDisposition.contains("filename") && !contentDisposition.contains("attachment")) {
            log.info("检测到直接URL编码的文件名，尝试直接解码");
            try {
                String decodedFileName = java.net.URLDecoder.decode(contentDisposition, "UTF-8");
                log.info("直接解码成功: {}", decodedFileName);
                return decodedFileName;
            } catch (Exception e) {
                log.warn("直接URL解码失败: {}", contentDisposition, e);
            }
        }

        String[] parts = contentDisposition.split(";");
        for (String part : parts) {
            part = part.trim();

            // 优先级1: 处理 filename*=UTF-8''encoded_filename 格式（RFC 5987标准）
            if (part.startsWith("filename*=UTF-8''")) {
                String encodedFileName = part.substring("filename*=UTF-8''".length()).trim();
                try {
                    String decodedFileName = java.net.URLDecoder.decode(encodedFileName, "UTF-8");
                    return decodedFileName;
                } catch (Exception e) {
                    log.warn("URL解码filename*=UTF-8''失败: {}", encodedFileName, e);
                    return encodedFileName;
                }
            }

            // 优先级2: 处理 filename*=UTF-8"encoded_filename" 格式
            else if (part.startsWith("filename*=UTF-8\"")) {
                String encodedFileName = part.substring("filename*=UTF-8\"".length()).trim();
                // 移除末尾的引号
                if (encodedFileName.endsWith("\"")) {
                    encodedFileName = encodedFileName.substring(0, encodedFileName.length() - 1);
                }
                try {
                    String decodedFileName = java.net.URLDecoder.decode(encodedFileName, "UTF-8");
                    return decodedFileName;
                } catch (Exception e) {
                    log.warn("URL解码filename*=UTF-8\"失败: {}", encodedFileName, e);
                    return encodedFileName;
                }
            }

            // 优先级3: 处理普通 filename="filename.ext" 格式
            else if (part.startsWith("filename=")) {
                String fileName = part.substring("filename=".length()).trim();

                // 移除引号
                if (fileName.startsWith("\"") && fileName.endsWith("\"")) {
                    fileName = fileName.substring(1, fileName.length() - 1);
                }

                // 尝试多种解码方式
                String decodedFileName = tryDecodeFileName(fileName);
                return decodedFileName;
            }
        }

        log.warn("无法从Content-Disposition中解析文件名: {}", contentDisposition);
        return "unknown_file.dat";
    }

    /**
     * 尝试多种方式解码文件名
     *
     * @param fileName 原始文件名
     * @return 解码后的文件名
     */
    private String tryDecodeFileName(String fileName) {
        // 方式1: 尝试URL解码
        try {
            String decoded = java.net.URLDecoder.decode(fileName, "UTF-8");
            if (!decoded.equals(fileName)) {
                return decoded;
            }
        } catch (Exception e) {
            // URL解码失败，继续尝试其他方式
        }

        // 方式2: 尝试ISO-8859-1到UTF-8转换
        try {
            String decoded = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
            if (!decoded.equals(fileName) && !decoded.contains("?")) {
                return decoded;
            }
        } catch (Exception e) {
            // ISO-8859-1到UTF-8转换失败，继续尝试其他方式
        }

        // 方式3: 直接返回原始文件名
        return fileName;
    }


    /**
     * 验证文件哈希值
     *
     * @param filePath  文件路径
     * @param fileHash  文件哈希值
     * @return 验证结果
     */
    private boolean validateFileHash(String filePath, String fileHash) {
        try {
            if (StringUtils.isEmpty(fileHash)) {
                log.warn("文件哈希值为空，跳过验证");
                return true; // 如果没有文件哈希值，默认通过验证
            }

            File file = new File(filePath);
            if (!file.exists()) {
                log.error("文件不存在: {}", filePath);
                return false;
            }

            // 使用FileHashUtil计算文件哈希值
            String actualHash = com.plpm.util.FileHashUtil.calculateSHA256(filePath);
            boolean isValid = fileHash.equals(actualHash);

            log.info("文件哈希验证 - 文件hash: {}, 实际hash: {}, 结果: {}", fileHash, actualHash, isValid ? "通过" : "失败");
            return isValid;

        } catch (Exception e) {
            log.error("文件哈希验证异常: {}", filePath, e);
            return false;
        }
    }


    /**
     * 保存FileRecordVO到数据库
     *
     * @param fileRecordVO 文件记录VO对象
     */
    private void saveFileRecord(FileRecordVO fileRecordVO) {
        try {
            // 使用FileRecordService保存
            com.plpm.service.FileRecordService.InsertResult result = fileRecordService.insertFileRecord(fileRecordVO);
            if (result.isSuccess()) {
                log.info("文件记录保存成功: {}", fileRecordVO.getFileName());
            } else {
                log.warn("文件记录保存失败: {}, 原因: {}", fileRecordVO.getFileName(), result.getMessage());
            }
        } catch (Exception e) {
            log.error("保存文件记录异常: {}", fileRecordVO.getFileName(), e);
        }
    }


    /**
     * 导入文件到IoTDB
     *
     * @param fileRecordVO  文件记录VO对象
     * @param localFilePath 本地文件路径
     */
    private void importFileToIoTDB(FileRecordVO fileRecordVO, String localFilePath) {
        try {
            log.info("开始导入文件到IoTDB: {}", fileRecordVO.getFileName());

            // 验证文件存在性
            if (!validateFileExists(localFilePath)) {
                handleImportFailure(fileRecordVO, "下载目录中文件不存在");
                return;
            }

            // 执行IoTDB导入
            java.util.Map<String, Object> result = executeIoTDBImport(fileRecordVO);

            // 处理导入结果，插入import_task表
            if ((Boolean) result.get("success")) {
                handleImportSuccess(fileRecordVO);
            } else {
                String errorMessage = (String) result.get("message");
                handleImportFailure(fileRecordVO, "导入失败: " + errorMessage);
            }

        } catch (Exception e) {
            log.error("导入文件到IoTDB异常: {}", fileRecordVO.getFileName(), e);
            handleImportFailure(fileRecordVO, "导入异常: " + e.getMessage());
        }
    }

    /**
     * 验证文件是否存在
     */
    private boolean validateFileExists(String localFilePath) {
        File file = new File(localFilePath);
        if (!file.exists()) {
            log.error("文件不存在，无法导入: {}", localFilePath);
            return false;
        }
        return true;
    }

    /**
     * 执行IoTDB导入
     */
    private java.util.Map<String, Object> executeIoTDBImport(FileRecordVO fileRecordVO) {
        java.util.List<FileRecordVO> fileRecordVOs = java.util.Collections.singletonList(fileRecordVO);
        return iotdbService.batchImportFilesToIoTDB(fileRecordVOs);
    }

    /**
     * 处理导入成功
     */
    private void handleImportSuccess(FileRecordVO fileRecordVO) {
        log.info("文件导入IoTDB成功: {}", fileRecordVO.getFileName());
        // 只插入import_task表，不更新file_record表
        createImportTaskRecord(fileRecordVO, 1, "导入成功");
    }

    /**
     * 处理导入失败
     */
    private void handleImportFailure(FileRecordVO fileRecordVO, String reason) {
        log.error("文件导入IoTDB失败: {}, 原因: {}", fileRecordVO.getFileName(), reason);
        // 只插入import_task表，不更新file_record表
        createImportTaskRecord(fileRecordVO, 2, reason);
    }


    /**
     * 删除文件（如果存在）
     *
     * @param filePath 文件路径
     */
    private void deleteFileIfExists(String filePath) {
        try {
            File file = new File(filePath);
            if (file.exists()) {
                boolean deleted = file.delete();
                if (deleted) {
                    log.info("成功删除文件: {}", filePath);
                } else {
                    log.warn("删除文件失败: {}", filePath);
                }
            }
        } catch (Exception e) {
            log.error("删除文件异常: {}", filePath, e);
        }
    }

    /**
     * 判断异常是否可重试
     *
     * @param e 异常对象
     * @return 是否可重试
     */
    private boolean isRetryableException(Exception e) {
        // 网络相关异常通常可以重试
        if (e instanceof java.net.ConnectException ||
                e instanceof java.net.SocketTimeoutException ||
                e instanceof java.net.UnknownHostException ||
                e instanceof java.io.IOException) {
            return true;
        }

        // 检查异常消息中是否包含可重试的关键词
        String message = e.getMessage();
        if (message != null) {
            String lowerMessage = message.toLowerCase();
            return lowerMessage.contains("timeout") ||
                    lowerMessage.contains("connection") ||
                    lowerMessage.contains("network") ||
                    lowerMessage.contains("unreachable");
        }

        return false;
    }

    // ===== 错误处理方法 =====

    /**
     * 处理任务失败
     *
     * @param exception 异常信息
     */
    private void handleTaskFailure(Exception exception) {
        log.error("PLPM文件下载任务失败", exception);

        // 记录任务失败指标
        recordTaskFailureMetrics(exception);

        // 发送告警通知（如果需要）
        sendFailureNotification(exception);
    }

    /**
     * 记录文件处理失败
     *
     * @param baseDataFile 基础数据文件对象
     * @param exception    异常信息
     * @param syncStatus   同步状态：0-未拉取，2-拉取失败
     */
    private void recordFileProcessingFailure(BaseDataFile baseDataFile, Exception exception, int syncStatus) {
        try {
            // 创建失败记录
            FileRecordVO failureRecord = createFailureRecord(baseDataFile, exception, syncStatus);

            // 保存失败记录到数据库
            saveFileRecord(failureRecord);

            log.warn("已记录文件处理失败，importFileIndex: {}, 原因: {}, syncStatus: {}",
                    baseDataFile.getImportFileIndex(), exception.getMessage(), syncStatus);

        } catch (Exception e) {
            log.error("记录文件处理失败时发生异常，importFileIndex: {}",
                    baseDataFile.getImportFileIndex(), e);
        }
    }

    /**
     * 获取完整的异常信息（包括异常类型和cause）
     *
     * @param exception 异常对象
     * @return 完整的异常信息字符串
     */
    private String getFullExceptionMessage(Exception exception) {
        if (exception == null) {
            return "处理失败: 未知异常";
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append("处理失败: [").append(exception.getClass().getSimpleName()).append("] ");
        sb.append(exception.getMessage());
        
        // 如果有cause，也包含进去
        Throwable cause = exception.getCause();
        if (cause != null) {
            sb.append(" | 原因: [").append(cause.getClass().getSimpleName()).append("] ");
            sb.append(cause.getMessage());
        }
        
        return sb.toString();
    }

    /**
     * 创建失败记录
     *
     * @param baseDataFile 基础数据文件对象
     * @param exception    异常信息
     * @param syncStatus   同步状态：0-未拉取，2-拉取失败
     */
    private FileRecordVO createFailureRecord(BaseDataFile baseDataFile, Exception exception, int syncStatus) {
        FileRecordVO failureRecord = new FileRecordVO();
        LocalDateTime now = LocalDateTime.now();

        // 设置基本信息
        failureRecord.setFileName(baseDataFile.getFileName());
        failureRecord.setProjectNo(baseDataFile.getProjectNo());
        failureRecord.setPlaneId(baseDataFile.getPlaneId());
        failureRecord.setPlaneNo(baseDataFile.getPlaneNo());
        failureRecord.setPlace(baseDataFile.getPlace());
        failureRecord.setCompany(baseDataFile.getCompany());
        failureRecord.setProductPhase(baseDataFile.getProductPhase());
        failureRecord.setDataTypeFirst(baseDataFile.getDataTypeFirst());
        failureRecord.setDataTypeSecond(baseDataFile.getDataTypeSecond());
        failureRecord.setDataSource(baseDataFile.getDataSource());
        
        // 设置时间信息
        if (baseDataFile.getStartTime() != null) {
            failureRecord.setStartTime(baseDataFile.getStartTime().toInstant()
                    .atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
        }
        failureRecord.setEngineId(baseDataFile.getEngineId());
        failureRecord.setEngineNo(baseDataFile.getEngineNo());
        failureRecord.setChannelNo(baseDataFile.getChannelNo());
        failureRecord.setEnginePlace(baseDataFile.getEnginePlace());
        failureRecord.setDeviceNo(baseDataFile.getDeviceNo());
        failureRecord.setEcuId(baseDataFile.getEcuId());
        failureRecord.setEcuNo(baseDataFile.getEcuNo());
        failureRecord.setEcuSysId(baseDataFile.getEcuSysId());
        failureRecord.setEcuSysNo(baseDataFile.getEcuSysNo());
        failureRecord.setSubject(baseDataFile.getSubject());
        failureRecord.setRemark(getFullExceptionMessage(exception));

        // 设置失败状态
        // failureRecord.setUploadStatus(2); // 上传失败
        failureRecord.setSyncStatus(syncStatus);   // 根据异常类型设置：0-未拉取，2-拉取失败
        failureRecord.setCreateTime(now);
        failureRecord.setUpdateTime(now);
        failureRecord.setUploadTime(now);
        failureRecord.setRetryTime(1);
        failureRecord.setSyncDataSource(1); // 1-苏家屯

        return failureRecord;
    }

    /**
     * 记录任务失败指标
     */
    private void recordTaskFailureMetrics(Exception exception) {
        // 这里可以集成监控系统，如Micrometer
        // meterRegistry.counter("plpm.task.failures", "exception", exception.getClass().getSimpleName()).increment();
        log.info("记录任务失败指标: {}", exception.getClass().getSimpleName());
    }

    /**
     * 发送失败通知
     */
    private void sendFailureNotification(Exception exception) {
        // 这里可以集成告警系统，如邮件、短信、钉钉等
        log.info("发送失败通知: {}", exception.getMessage());
    }

    /**
     * 创建import_task记录
     *
     * @param fileRecordVO 文件记录VO对象
     * @param importStatus 导入状态：1-成功，2-失败
     * @param remark       备注信息
     */
    private void createImportTaskRecord(FileRecordVO fileRecordVO, int importStatus, String remark) {
        try {
            log.info("开始创建import_task记录，文件名: {}, 状态: {}", fileRecordVO.getFileName(), importStatus);

            // 创建并配置ImportTask
            ImportTask importTask = createAndConfigureImportTask(fileRecordVO, importStatus, remark);

            // 创建import_task记录
            boolean result = importTaskService.createImportTask(importTask);
            logImportTaskResult(result, fileRecordVO.getFileName(), importStatus);

        } catch (Exception e) {
            log.error("创建import_task记录异常，文件名: {}, 状态: {}", fileRecordVO.getFileName(), importStatus, e);
        }
    }

    /**
     * 创建并配置ImportTask对象
     */
    private ImportTask createAndConfigureImportTask(FileRecordVO fileRecordVO, int importStatus, String remark) {
        // 将FileRecordVO转换为ImportTask
        ImportTask importTask = convertFileRecordVOToImportTask(fileRecordVO);

        // 处理重试次数
        setRetryTime(importTask, fileRecordVO.getFileName());

        // 设置导入状态和备注
        importTask.setImportStatus(importStatus);
        importTask.setRemark(remark);

        return importTask;
    }

    /**
     * 设置重试次数
     */
    private void setRetryTime(ImportTask importTask, String fileName) {
        ImportTask existingTask = importTaskService.findSuccessImportByFileName(fileName);
        if (existingTask != null) {
            // 如果已存在，retrytime +1
            importTask.setRetryTime(existingTask.getRetryTime() + 1);
        } else {
            // 如果不存在，retrytime为1
            importTask.setRetryTime(1);
        }
    }

    /**
     * 记录ImportTask创建结果
     */
    private void logImportTaskResult(boolean result, String fileName, int importStatus) {
        if (result) {
            log.info("成功创建import_task记录，文件名: {}, 状态: {}", fileName, importStatus);
        } else {
            log.warn("创建import_task记录失败，文件名: {}, 状态: {}", fileName, importStatus);
        }
    }

    /**
     * 将FileRecordVO转换为ImportTask
     *
     * @param fileRecordVO 文件记录VO对象
     * @return ImportTask对象
     */
    private ImportTask convertFileRecordVOToImportTask(FileRecordVO fileRecordVO) {
        ImportTask importTask = new ImportTask();

        // 复制基本信息
        importTask.setFileName(fileRecordVO.getFileName());
        importTask.setProjectNo(fileRecordVO.getProjectNo());
        importTask.setPlaneId(fileRecordVO.getPlaneId());
        importTask.setPlaneNo(fileRecordVO.getPlaneNo());
        importTask.setPlace(fileRecordVO.getPlace());
        importTask.setCompany(fileRecordVO.getCompany());
        importTask.setProductPhase(fileRecordVO.getProductPhase());
        importTask.setDataTypeFirst(fileRecordVO.getDataTypeFirst());
        importTask.setDataTypeSecond(fileRecordVO.getDataTypeSecond());
        importTask.setDataSource(fileRecordVO.getDataSource());
        importTask.setStartTime(fileRecordVO.getStartTime());
        importTask.setEngineId(fileRecordVO.getEngineId());
        importTask.setEngineNo(fileRecordVO.getEngineNo());
        importTask.setChannelNo(fileRecordVO.getChannelNo());
        importTask.setDeviceNo(fileRecordVO.getDeviceNo());
        importTask.setEnginePlace(fileRecordVO.getEnginePlace());
        importTask.setEcuId(fileRecordVO.getEcuId());
        importTask.setEcuNo(fileRecordVO.getEcuNo());
        importTask.setEcuSysId(fileRecordVO.getEcuSysId());
        importTask.setEcuSysNo(fileRecordVO.getEcuSysNo());
        importTask.setSubject(fileRecordVO.getSubject());
        importTask.setCheckCode(fileRecordVO.getCheckCode());
        importTask.setSyncDataSource(fileRecordVO.getSyncDataSource());

        // 设置默认值
        importTask.setSyncStatus(0); // 默认未同步
        importTask.setCreateTime(LocalDateTime.now()); // 设置创建时间
        importTask.setUpdateTime(LocalDateTime.now()); // 设置更新时间

        return importTask;
    }


}
